package naga.x.event;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;

public class EventManager {
	
	private static final Logger LOG = LoggerFactory.getLogger(EventManager.class);
	
	private TIntObjectMap<List<EventListener>> listeners = new TIntObjectHashMap<List<EventListener>>();
	private Executor executor = new ThreadPoolExecutor(2, 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

	public EventManager() {}
	
	public synchronized void register(EventListener listener) {
		TIntObjectMap<List<EventListener>> ls = listeners;
		int[] types = listener.getEventTypes();
		for (int type : types) {
			List<EventListener> list = ls.get(type);
			if (list == null) {
				list = new CopyOnWriteArrayList<EventListener>();
				ls.put(type, list);
			}
			list.add(listener);
		}
	}
	
	public synchronized void unRegister(EventListener listener) {
		TIntObjectMap<List<EventListener>> ls = listeners;
		int[] types = listener.getEventTypes();
		for (int type : types) {
			List<EventListener> list = ls.get(type);
			if (list != null) {
				list.remove(listener);
				if (list.isEmpty()) {
					ls.remove(type);
				}
			}
		}
	}
	
	public synchronized void unRegister(int type) {
		listeners.remove(type);
	}
	
	public void fireEvent(int type, Object... params) {
		fireEvent(new Event(type, params));
	}
	
	public void fireEvent(Event event) {
		List<EventListener> list = listeners.get(event.getType());
		if (list != null) {
			for (int i = 0; i < list.size(); i++) {
				try {
					list.get(i).handleEvent(event);
				} catch (Exception e) {
					LOG.error(e.getMessage(), e);
				}
			}
		}
	}
	
	public void addEvent(int type, Object... params) {
		addEvent(new Event(type, params));
	}
	
	public void addEvent(final Event event) {
		executor.execute(new Runnable() {
			@Override
			public void run() {
				fireEvent(event);
			}
		});
	}
	
}
